<template>
  <div>
    <div class="signature" :style="{ width: dialogParams.width, height: dialogParams.height }">
      <canvas style="touch-action: none; user-select: none" ref="signatureArea"></canvas>
    </div>
    <div class="btn">
      <el-button @click="exportImage" v-if="debug">导出</el-button>
      <el-button @click="clear">清空</el-button>
      <el-button type="primary" @click="onSubmit">完成</el-button>
    </div>
  </div>
</template>

<script setup lang="ts">
import SignaturePad from 'signature_pad';
import { DialogProp } from '@/components/Dialog/types';
import { useThirdParty } from '@/components/thirdParty/hooks';
import { ThirdProps } from '@/components/thirdParty/types';
import { ANY_OBJECT } from '@/types/generic';
import { Bound, SignatureInfo } from './signature';

const emit = defineEmits<{ result: [SignatureInfo | undefined] }>();

interface IProps extends ThirdProps {
  value?: string;
  originBound?: Bound;
  width?: string | number;
  height?: string | number;
  backgroundColor?: string;
  penColor?: string;
  autoTrim?: boolean;
  debug?: boolean;
  // 当使用Dialog.show弹出组件时，须定义该prop属性，以便对dialog进行回调
  dialog?: DialogProp<SignatureInfo | undefined>;
}
const props = withDefaults(defineProps<IProps>(), {
  width: '500px',
  height: '200px',
  penColor: '#000',
  autoTrim: false,
  debug: false,
});

const signatureArea = ref<HTMLCanvasElement>();
let canvas: HTMLCanvasElement | undefined = undefined;
let signaturePad: SignaturePad | null = null;

const { thirdParams, onCloseThirdDialog } = useThirdParty(props);

const dialogParams = computed(() => {
  return {
    value: props.value || thirdParams.value.value,
    originBound: props.originBound || thirdParams.value.originBound,
    width: props.width || thirdParams.value.width || '500px',
    height: props.height || thirdParams.value.height || '200px',
    backgroundColor: props.backgroundColor || thirdParams.value.backgroundColor,
    penColor: props.penColor || thirdParams.value?.penColor || '#000',
    autoTrim: props.autoTrim || thirdParams.value.autoTrim || false,
    debug: props.debug || thirdParams.value.debug || false,
  };
});

function resizeCanvas() {
  if (!canvas) return;
  // When zoomed out to less than 100%, for some very strange reason,
  // some browsers report devicePixelRatio as less than 1
  // and only part of the canvas is cleared then.
  const ratio = Math.max(window.devicePixelRatio || 1, 1);

  if (dialogParams.value.debug) {
    console.log('signaturePad.resizeCanvas() ratio', ratio);
    console.log('signaturePad.resizeCanvas() offsetWidth', canvas?.offsetWidth);
    console.log('signaturePad.resizeCanvas() offsetHeight', canvas?.offsetHeight);
  }

  // This part causes the canvas to be cleared
  canvas.width = canvas.offsetWidth * ratio;
  canvas.height = canvas.offsetHeight * ratio;
  canvas.getContext('2d')?.scale(ratio, ratio);
  // canvas.style.width = canvas.offsetWidth + 'px'; // 设置CSS宽度，以像素为单位，与实际宽度一致以保持清晰度在高清屏上。
  // canvas.style.height = canvas.offsetHeight + 'px'; // 同上。

  // This library does not listen for canvas changes, so after the canvas is automatically
  // cleared by the browser, SignaturePad#isEmpty might still return false, even though the
  // canvas looks empty, because the internal data of this library wasn't cleared. To make sure
  // that the state of this library is consistent with visual state of the canvas, you
  // have to clear it manually.
  // ignaturePad.clear();

  // If you want to keep the drawing on resize instead of clearing it you can reset the data.
  // this.signaturePad.fromData(this.signaturePad.toData());
}

onMounted(() => {
  const options = {
    // It's Necessary to use an opaque color when saving image as JPEG;
    // this option can be omitted if only saving as PNG or SVG
    // backgroundColor: 'rgb(255, 255, 255)'
    backgroundColor: dialogParams.value.backgroundColor,
    penColor: dialogParams.value.penColor,
  };
  nextTick(() => {
    canvas = signatureArea.value;
    if (canvas) {
      signaturePad = new SignaturePad(canvas, options);
      // 只合适Base64图片
      if (dialogParams.value.value) {
        const ratio = Math.max(window.devicePixelRatio || 1, 1);
        const options: ANY_OBJECT = {};
        let img = new Image();
        img.src = dialogParams.value.value;
        img.onload = () => {
          options.width = img.width;
          options.height = img.height;
          options.xOffset = 0;
          options.yOffset = 0;
          signaturePad.fromDataURL(dialogParams.value.value, options);
        };
        img.onerror = () => {
          console.error('图片加载失败');
        };
      }
      resizeCanvas();
    }
  });
});

const clear = () => {
  if (!signaturePad) {
    console.error('signaturePad is null');
    return;
  }
  signaturePad.clear();
};

let bound: Bound;

function getTrimmedCanvas() {
  if (!canvas) {
    console.error('canvas is null');
    return;
  }
  const ctx = canvas.getContext('2d');
  if (!ctx) {
    console.error("canvas.getContext('2d') is null");
    return;
  }
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const pixels = imageData.data;
  const l = pixels.length;
  let top: number | null = null;
  let left: number | null = null;
  let right: number | null = null;
  let bottom: number | null = null;
  bound = { top: 0, left: 0, right: canvas.width, bottom: canvas.height };

  // 找到签名的实际边界
  for (let i = 0; i < l; i += 4) {
    if (pixels[i + 3] !== 0) {
      // 检查 alpha 通道是否不透明
      const x = (i / 4) % canvas.width;
      const y = ~~(i / 4 / canvas.width);

      if (top === null) {
        top = y;
        bound.top = y;
      } else if (y < bound.top) {
        bound.top = y;
      }

      if (left === null) {
        left = x;
        bound.left = x;
      } else if (x < bound.left) {
        bound.left = x;
      }

      if (right === null) {
        right = x;
        bound.right = x;
      } else if (x > bound.right) {
        bound.right = x;
      }

      if (bottom === null) {
        bottom = y;
        bound.bottom = y;
      } else if (y > bound.bottom) {
        bound.bottom = y;
      }
    }
  }

  // 如果没有找到任何签名内容，返回 null
  if (top === null) {
    return null;
  }

  // 创建一个新的 canvas 对象
  const trimmedCanvas = document.createElement('canvas');
  const trimmedCtx = trimmedCanvas.getContext('2d');

  // 设置新的 canvas 的宽度和高度
  trimmedCanvas.width = bound.right - bound.left + 1;
  trimmedCanvas.height = bound.bottom - bound.top + 1;

  // 复制签名内容到新的 canvas 上
  trimmedCtx?.putImageData(
    ctx.getImageData(bound.left, bound.top, trimmedCanvas.width, trimmedCanvas.height),
    0,
    0,
  );

  return trimmedCanvas;
}

function toDataURL() {
  const trimmedCanvas = props.autoTrim ? getTrimmedCanvas() : canvas;
  // type 是可选参数，表示图像类型，默认为'image/png'。常见的MIME类型还包括'image/jpeg'和'image/webp'。
  // encoderOptions是可选参数，表示图像质量，取值范围为0到1，只对'image/jpeg'和'image/webp'有效，默认为0.92。
  //const dataURL = trimmedCanvas.toDataURL('image/png', { type: 'image/png', encoderOptions: 1.0 });
  const dataURL = trimmedCanvas?.toDataURL();
  //const dataURL = signaturePad.toDataURL();
  //const dataURL = signaturePad.toDataURL("image/jpeg");
  //const dataURL = signaturePad.toDataURL('image/svg+xml');
  //const dataURL = signaturePad.toDataURL('image/svg+xml', { includeBackgroundColor: true });
  if (props.debug) {
    console.log('signaturePad.toDataURL()', dataURL);
  }
  return dataURL;
}

const onSubmit = () => {
  if (!signaturePad) {
    console.error('signaturePad is null');
    return;
  }
  if (signaturePad.isEmpty()) {
    console.warn('没有签名内容，数据无效');
  } else {
    const dataURL = toDataURL();
    const result: SignatureInfo = {
      data: dataURL,
      bound: bound || {
        top: 0,
        left: 0,
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        right: canvas!.width,
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        bottom: canvas!.height,
      },
    };
    emit('result', result);
    if (props.dialog) {
      props.dialog.submit(result);
    } else {
      onCloseThirdDialog(true, null, result);
    }
  }
};

function dataURLToBlob(dataURL) {
  // Code taken from https://github.com/ebidel/filer.js
  const parts = dataURL.split(';base64,');
  const contentType = parts[0].split(':')[1];
  const raw = window.atob(parts[1]);
  const rawLength = raw.length;
  const uInt8Array = new Uint8Array(rawLength);

  for (let i = 0; i < rawLength; ++i) {
    uInt8Array[i] = raw.charCodeAt(i);
  }

  return new Blob([uInt8Array], { type: contentType });
}

function download(dataURL, filename) {
  const blob = dataURLToBlob(dataURL);
  const url = window.URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.style.display = 'none';
  a.href = url;
  a.download = filename;

  document.body.appendChild(a);
  a.click();

  window.URL.revokeObjectURL(url);
}

const exportImage = () => {
  if (!signaturePad) {
    console.error('signaturePad is null');
    return;
  }
  if (signaturePad.isEmpty()) {
    console.warn('没有签名内容，请先手写一个签名再导出');
  } else {
    const dataURL = toDataURL();
    download(dataURL, 'signature.png');
  }
};
</script>

<style scoped>
.signature {
  border: 1px solid #999;
  padding: 3px;
}

canvas {
  border: 1px dashed #aaa;
  width: 100%;
  height: 100%;
}

.btn {
  margin-top: 10px;
  display: flex;
  flex-direction: row;
  justify-content: right;
}
</style>
